import ejs from 'ejs';
import pkg from '../../../../package.json';
import {
	TASK_KEY_LABEL_MAP,
	WARN_LEVEL_MAP,
	TASK_TYPE_CATEGORY_MAP,
	TASK_CATEGORY_KEY_LABEL_MAP,
	AVILIABLE_TASK_MAP,
	WARNING_CATEGORY_KEY_LABEL_MAP,
} from '../../../common/constants';
import { datetime, duration } from '../../../common/formatter';
import errorCodesBuilder from '../../helpers/errorCodesBuilder';
import collectType from '../../../common/collectType';

const { name } = pkg;
const errorCodes = errorCodesBuilder();

function getTemplate(
	{
		_id,
		name,
		target,
		taskType,
		securityStatus,
		endTime,
		result,
		triggerType,
		collect,
	},
	job,
	alertState
) {
	if (securityStatus === 'safe') {
		return `
<div style="text-indent:2em;line-height: 2;">您好！</div>
<div></div>
<div style="line-height: 2;font-size: 14px;text-indent:2em;">
您的资产
<span style="color:#0B6FD6">${job.note} (${job.targetUrl})<span>
监测的${TASK_CATEGORY_KEY_LABEL_MAP[TASK_TYPE_CATEGORY_MAP[taskType]]}
${TASK_KEY_LABEL_MAP[taskType]}任务：
<span style="color:#0B6FD6">${name} (${target})<span>
，于 <span style="color:#0B6FD6">${datetime(endTime)}</span> 恢复正常。
</div>
</br>
<div style="width: 100%: height: 42px; line-heihgt: 40px; border: 1px solid #CBD1DA; border-radius: 2px;">
异常持续时间：
${duration(
	new Date(endTime).getTime() - new Date(alertState.firstSendTime).getTime()
)}
</div>
</br>
<div style="text-align: center;">
	<a style="display: inline-block; padding: 12px 30px; background: #0B6FD6; color: #fff; border-radius: 2px;" href="<%= serviceUrl %>/${
		TASK_TYPE_CATEGORY_MAP[taskType]
	}/${taskType}/detail/${_id}">
		查看详情
	</a>
</div>
			`.trim();
	}

	const initWarningLevels = Object.values(WARN_LEVEL_MAP).reduce((obj, w) => {
		obj[w] = 0;
		return obj;
	}, {});
	const warnLevels = result.warnings.reduce((obj, x) => {
		obj[WARN_LEVEL_MAP[x.level]]++;
		return obj;
	}, initWarningLevels);
	switch (taskType) {
		case 'vul':
		case 'ssl':
			return `
<div style="text-indent:2em;line-height: 2;">您好！</div>
<div style="text-indent:2em;line-height: 2;font-size: 14px;">
	您的资产<span style="color:#0B6FD6; padding: 0 0.125em">
		${job.note} (${job.targetUrl})
	</span>
	于<span style="color:#0B6FD6; padding: 0 0.125em">
		${datetime(endTime)}
	</span>已完成
	<span style="padding: 0 0.125em">
		${collectType({ triggerType, collect })}${TASK_KEY_LABEL_MAP[taskType]}
	</span>
	监测，本次监测共发现
	<span style="color:#0B6FD6">
		${Object.entries(warnLevels)
			.map(([label, num]) => {
				return `<span style="color:#0B6FD6">${num}</span>个${label}漏洞`;
			})
			.join('、')}
	</span>，为了您的资产安全，请及时修复并加强安全防护体系。
</div>
</br>
</br>
<div style="text-align: center;">
	<a style="display: inline-block; padding: 12px 30px; background: #0B6FD6; color: #fff; border-radius: 2px;"
		href="<%= serviceUrl %>/${
			TASK_TYPE_CATEGORY_MAP[taskType]
		}/${taskType}/detail/${_id}"
	>
		查看详情
	</a>
</div>
			`.trim();
		case 'securityEvent':
			return `
<div style="text-indent:2em;line-height: 2;">您好！</div>
<div style="text-indent:2em;line-height: 2;font-size: 14px;">
	您的资产
	<span style="color:#0B6FD6; padding: 0 0.125em;">
		${job.note} (${job.targetUrl})
	</span>于<span style="color:#0B6FD6; padding: 0 0.125em;">
		${datetime(endTime)}
	</span>完成<span style="padding: 0 0.125em;">
		${collectType({ triggerType, collect })}${TASK_KEY_LABEL_MAP[taskType]}
	</span>监测。本次监测共发现<span style="color:#0B6FD6">
	${result.warnings.length}</span>个安全事件，<span>
		${Object.entries(
			result.warnings.reduce((obj, x) => {
				obj[x.title] = obj[x.title] || 0;
				obj[x.title] += 1;
				return obj;
			}, {})
		)
			.map(
				([label, num]) => `${label}<span style="color:#0B6FD6">${num}</span>个`
			)
			.join('、')}
	</span>，为了您的资产安全，请及时修复并加强安全防护体系。
</div>
</br>
</br>
<div style="text-align: center;">
	<a
		style="display: inline-block; padding: 12px 30px; background: #0B6FD6; color: #fff; border-radius: 2px;"
		href="<%= serviceUrl %>/${
			TASK_TYPE_CATEGORY_MAP[taskType]
		}/${taskType}/detail/${_id}"
	>
		查看详情
	</a>
</div>
			`.trim();
		case 'http':
		case 'ping':
			return `
<div style="text-indent:2em;line-height: 2;">您好！</div>
<div style="text-indent: 2em;line-height: 2;">
您的资产
<span style="color:#0B6FD6; padding: 0 0.125em;">
	${job.note}(${job.targetUrl})
</span>监测的可用性${TASK_KEY_LABEL_MAP[taskType]}任务：
<span style="color:#0B6FD6; padding: 0 0.125em;">${name} (${target})</span>
，于 <span style="color:#0B6FD6; padding: 0 0.125em;">
	${datetime(endTime)}
</span>至少有以下<span style="color:#0B6FD6; padding: 0 0.125em;">
	${result.addition.nodeAbnormalCount}
</span>
个探测点异常。
</div>
</br>
<table style="width: 100%;margin: 0 auto; border-spacing: 0 8px; border-collapse: separate; font-size: 14px;">
	<% content.forEach(function(w) { %>
		<% w.detail.area.forEach(function(area) { %>
		<tr style="line-height: 42px;">
			<td style="border: 1px solid #CBD1DA; border-right: 0; padding-left: 20px; border-radius: 4px 0 0 4px;"><%= area %></td>
			<td style="border: 1px solid #CBD1DA; border-left: 0; padding-right: 20px; border-radius: 0 4px 4px 0; text-align: right;">
				<img width="16" height="16" style="margin-right:20px;vertical-align: text-bottom;" src="" />
				<%= w.title %>
			</td>
		</tr>
		<% }) %>
	<% }) %>
</table>
</br>
<div style="text-align: center;">
	<a style="display: inline-block; padding: 12px 30px; background: #0B6FD6; color: #fff; border-radius: 2px;" href="<%= serviceUrl %>/${
		TASK_TYPE_CATEGORY_MAP[taskType]
	}/${taskType}/detail/${_id}"
	>
		查看详情
	</a>
</div>
			`.trim();
		case 'asset':
			return `
<div style="text-indent:2em;line-height: 2;">您好！</div>
<div></div>
<div style="text-indent:2em;line-height: 2;font-size: 14px;">
	您的资产
	<span style="color:#0B6FD6; padding: 0 0.125em;">
		${job.note} (${job.targetUrl})
	</span>于<span style="color:#0B6FD6; padding: 0 0.125em;">
		${datetime(endTime)}
	</span>
	<span style="padding: 0 0.125em;">
		${collectType({
			triggerType,
			collect,
		})}${TASK_KEY_LABEL_MAP[taskType]}
	</span>监测中发现<span style="color:#0B6FD6; padding: 0 0.125em;">
		${result.warningCount || 0}
	</span>个变动。为了您的资产安全请及时确认。
</div>
</br>
</br>
<div style="text-align: center;">
	<a style="display: inline-block; padding: 12px 30px; background: #0B6FD6; color: #fff; border-radius: 2px;" href="<%= serviceUrl %>/${
		TASK_TYPE_CATEGORY_MAP[taskType]
	}/${taskType}/detail/${_id}">
		查看详情
	</a>
</div>
			`.trim();
	}
}

function titleBuilder({ securityStatus, taskType, warnings }) {
	if (securityStatus === 'abnormalResolved') {
		switch (taskType) {
			case 'http':
			case 'ping':
				return '服务恢复';
			default:
				return '异常恢复';
		}
	}

	if (securityStatus === 'safe') {
		return '未监测到异常';
	}

	const categoryMap = warnings.reduce(
		(obj, { category, isResolved, ignoredTime }) => {
			if (isResolved || ignoredTime || !category) return obj;
			obj[category] = true;
			return obj;
		},
		{}
	);

	if (taskType === 'vul') return '发现站点存在Web漏洞';

	const warningCategoryMap = WARNING_CATEGORY_KEY_LABEL_MAP[taskType];

	let ret = `发现站点存在${
		TASK_CATEGORY_KEY_LABEL_MAP[TASK_TYPE_CATEGORY_MAP[taskType]]
	}异常`;

	if (warningCategoryMap) {
		ret = `发现站点存在${Object.keys(categoryMap).map(
			key => warningCategoryMap[key]
		)}`;
		switch (TASK_TYPE_CATEGORY_MAP[taskType]) {
			case 'securityEvent':
				ret += '异常';
				break;
			case 'asset':
				ret += '变动';
				break;
			default:
		}
	}
	return ret;
}

function alertMessageBuilder({
	task,
	job,
	warnings,
	config,
	alertState,
	securityStatus,
}) {
	const template = getTemplate(task, job, alertState);
	const category =
		TASK_CATEGORY_KEY_LABEL_MAP[TASK_TYPE_CATEGORY_MAP[task.taskType]];

	const title = titleBuilder({
		securityStatus,
		warnings,
		taskType: task.taskType,
	});

	if (securityStatus !== 'warning') {
		const status =
			securityStatus === 'abnormalResolved' ? '异常恢复' : '未监测到异常';
		return {
			title,
			summary: `尊敬的用户您好，知道创宇云安全监测服务发现您的网站 ${job.note} (${task.target})${category}${status}，您可前往告警详情查看具体内容。`,
			content: ejs.render(template, {
				title,
				serviceUrl: config.serviceUrl,
				content: task.result?.addition?.detail || [],
			}),
			duration: 0,
			smsTemplate: {
				id: config.yunpianTplIdAviliable,
				value: {
					'#domain#': `${job.note} (${job.targetUrl})`,
					'#url#': `${task.name} (${task.target})`,
					'#time#': datetime(task.endTime),
					'#type#': task.taskType,
					'#num#': task.result?.addition?.nodeTotalCount || '',
					'#details#': '恢复正常',
				},
			},
			detail: {
				securityStatus,
				taskCategory: TASK_TYPE_CATEGORY_MAP[task.taskType],
				warningCount: warnings.length,
				taskType: task?.taskType,
				taskId: task?._id,
			},
		};
	}

	let summary;
	let smsTemplate = {};
	switch (task.taskType) {
		case 'http':
		case 'ping':
			summary = `${job.note} (${job.targetUrl})监测的可用性${
				TASK_KEY_LABEL_MAP[task.taskType]
			}任务：${task.name} (${task.target})，于 ${datetime(
				task.endTime
			)} 发现有 ${task.result.addition.nodeAbnormalCount} 个监测点异常。`;
			smsTemplate = {
				id: config.yunpianTplIdAviliable,
				value: {
					'#domain#': `${job.note} (${job.targetUrl})`,
					'#url#': `${task.name} (${task.target})`,
					'#time#': datetime(task.endTime),
					'#type#': task.taskType,
					'#num#': task.result.addition.nodeAbnormalCount,
					'#details#': '异常',
				},
			};
			break;
		case 'asset':
			// FIXME: 需要补充变动详情
			// eslint-disable-next-line prettier/prettier
			summary = `${job.note} (${job.targetUrl}) 于 ${datetime(task.endTime)} 系统资产变动监测中发现资产有更新。`;
			smsTemplate = {
				id: config.yunpianTplIdAsset,
				value: {
					'#domain#': `${job.note} (${job.targetUrl})`,
					'#time#': datetime(task.endTime),
					'#details#': '资产有更新',
				},
			};
			break;
		case 'vul':
			// eslint-disable-next-line prettier/prettier
			summary = `${job.note} (${job.targetUrl}) 于 ${datetime(task.endTime)} ${collectType({ triggerType: task.triggerType, collect: task.taskSettings.collect })}${TASK_KEY_LABEL_MAP[task.taskType]}监测中发现 ${task.result.warningCount} 个漏洞威胁，请及时修复。`;
			smsTemplate = {
				id: config.yunpianTplIdWeakness,
				value: {
					'#domain#': `${job.note} (${job.targetUrl})`,
					'#time#': datetime(task.endTime),
					'#way#': collectType({
						triggerType: task.triggerType,
						collect: task.taskSettings.collec,
					}),
					'#type#': TASK_KEY_LABEL_MAP[task.taskType],
					'#details#': `${task.result.warningCount}个漏洞威胁`,
				},
			};
			break;
		case 'ssl':
			// eslint-disable-next-line prettier/prettier
			summary = `${job.note} (${job.targetUrl}) 于 ${datetime(task.endTime)} ${TASK_KEY_LABEL_MAP[task.taskType]}监测中发现 ${task.result.warningCount} 个漏洞威胁，请及时修复。`;
			smsTemplate = {
				id: config.yunpianTplIdWeakness,
				value: {
					'#domain#': `${job.note} (${job.targetUrl})`,
					'#time#': datetime(task.endTime),
					'#way#': ' ',
					'#type#': TASK_KEY_LABEL_MAP[task.taskType],
					'#details#': `${task.result.warningCount}个漏洞威胁`,
				},
			};
			break;
		case 'securityEvent':
			// eslint-disable-next-line prettier/prettier
			summary = `${job.note} (${job.targetUrl})于 ${datetime(task.endTime)} ${TASK_KEY_LABEL_MAP[task.taskType]}监测中发现 ${task.result.warningCount} 个安全事件异常，请及时修复。`;
			smsTemplate = {
				id: config.yunpianTplIdWeakness,
				value: {
					'#domain#': `${job.note} (${job.targetUrl})`,
					'#time#': datetime(task.endTime),
					'#way#': ' ',
					'#type#': TASK_KEY_LABEL_MAP[task.taskType],
					'#details#': `${task.result.warningCount}个安全事件异常`,
				},
			};
			break;
	}

	const content = ejs.render(template, {
		title,
		serviceUrl: config.serviceUrl,
		content: warnings.map(x => ({
			...x,
			level: WARN_LEVEL_MAP[x.level],
		})),
	});

	return {
		smsTemplate,
		title,
		summary,
		content,
		detail: {
			securityStatus,
			taskCategory: TASK_TYPE_CATEGORY_MAP[task.taskType],
			warningCount: warnings.length,
			taskType: task?.taskType,
			taskId: task?._id,
		},
	};
}

export default async function alert({
	task: taskObj,
	ref: refObj,
	job,
	server,
	taskIdx,
}) {
	const {
		app: {
			model: { Alert, Job, Task },
			config: { [name]: config },
		},
		methods: { sendAlert },
	} = server;
	const task = taskObj?.toObject ? taskObj.toObject() : taskObj;
	const ref = refObj?.toObject ? refObj.toObject() : refObj;
	const { taskType } = task;

	// 未启用告警
	if (!task.alertSettings.enable[task.taskType]) return;

	const warnings = task?.result?.warnings || [];
	const isAviableTask = AVILIABLE_TASK_MAP[task.taskType];
	const browserDetailLink = `/${TASK_TYPE_CATEGORY_MAP[taskType]}/${taskType}/detail/${task._id}`;
	const link = `${config.serviceUrl}${browserDetailLink}`;

	let alertState =
		taskType === 'asset'
			? ref.alertState
			: isAviableTask
			? ref[`${taskType}AlertState`][taskIdx]
			: ref[`${taskType}AlertState`];
	alertState = alertState || { sendCount: 0 };
	let securityStatus = task.securityStatus;
	if (alertState.securityStatus === 'warning' && securityStatus === 'safe') {
		// 异常恢复
		securityStatus = 'abnormalResolved';
	}
	if (!alertState.firstSendTime) {
		alertState.firstSendTime = task.endTime;
	}
	alertState.lastSendTime = task.endTime;
	alertState.sendCount = alertState.sendCount + 1;
	alertState.securityStatus = task.securityStatus;

	if (
		securityStatus === 'safe' ||
		(securityStatus === 'abnormalResolved' && !AVILIABLE_TASK_MAP[taskType])
	) {
		return;
	}

	const { title, summary, content, detail, smsTemplate } = alertMessageBuilder({
		warnings,
		task,
		job,
		alertState,
		config,
		securityStatus,
	});

	try {
		let duration = 0;
		if (securityStatus === 'abnormalResolved') {
			duration = parseInt(
				new Date(alertState.lastSendTime).getTime() -
					new Date(alertState.firstSendTime).getTime(),
				10
			);
		}
		const isResolved = warnings.every(({ isResolved }) => isResolved === true);

		const taskTriggerType = task.triggerType;

		await Promise.all([
			new Alert({
				title,
				summary,
				content,
				detail,
				taskType,
				isResolved,
				taskTriggerType,
				securityStatus,
				domain: task.domain,
				target: task.target,
				jobId: task.jobId,
				refId: task.refId,
				taskId: task._id,
				taskCategory: TASK_TYPE_CATEGORY_MAP[task.taskType],
				alertType: task.securityStatus === 'warning' ? 'abnormal' : 'normal',
				taskName: task.name,
				uid: task.uid,
				duration: Number.isNaN(duration) ? 0 : duration,
			}).save(),
			Task.updateOne({ _id: task._id }, { $set: { isSendAlert: true } }),
		]);
	} catch (err) {
		const error = errorCodes['[0A][告警列表]告警列表新增信息失败'].from(err);
		server.log.error({ error }, error.message);
		throw error;
	}

	if (
		AVILIABLE_TASK_MAP[taskType] &&
		securityStatus === 'warning' &&
		task.result.addition.nodeAbnormalCount < task.taskSettings.alert.nodeCount
	) {
		// 小于设置的最小告警节点数
		// eslint-disable-next-line
		server.log.debug({ title, taskType, target: task.target }, ' 异常节点小于最小数量跳过告警');
		return;
	}

	if (securityStatus === 'abnormalResolved') {
		try {
			await Job.updateOne(
				{ _id: ref._id },
				{
					$set: {
						[`${
							isAviableTask
								? `${taskType}AlertState.${taskIdx}`
								: `${taskType}AlertState`
						}`]: {
							firstSendTime: null,
							endSendTime: null,
							sendCount: 0,
							securityStatus: 'safe',
						},
					},
				}
			);
		} catch (err) {
			const error = errorCodes[
				'[0E][发送告警恢复信息]初始化任务告警状态失败'
			].from(err);
			server.log.error({ error }, error.rawMessage);
			throw error;
		}

		try {
			await sendAlert({
				task,
				title,
				content,
				summary,
				detail,
				smsTemplate,
				link,
				job,
				securityStatus,
			});
			server.methods.sendNotification({
				title,
				category: '告警通知',
				to: [task.uid],
				message: summary,
				data: detail,
			});
		} catch (err) {
			const error = errorCodes['[0A][告警列表]告警列表新增信息失败'].from(err);
			server.log.error({ error }, error.rawMessage);
			throw error;
		}

		return;
	}

	const alertCount = task.taskSettings.alert?.alertCount;
	if (
		(task.triggerType === 'schedule' ||
			task.triggerType === 'scheduleIncrement') &&
		alertCount
	) {
		// 异常告警次数限制
		if (alertState.sendCount > alertCount) {
			server.log.info(
				{
					title,
					alertCount,
					sendCount: alertState.sendCount,
					target: task.target,
					taskType: task.taskType,
				},
				'超过任务异常告警次数限制，不发送告警通知'
			);
			return;
		}
	}

	let receivers;
	try {
		receivers = await sendAlert({
			task,
			title,
			content,
			summary,
			smsTemplate,
			link,
			job,
		});
	} catch (err) {
		const error = errorCodes[
			'[0A][发送告警信息]告警邮件/短信/微信发送失败'
		].from(err);
		server.log.error({ error }, error.message);
	}

	try {
		await Job.updateOne(
			{ _id: ref._id },
			{
				$set: {
					[`${
						isAviableTask
							? `${taskType}AlertState.${taskIdx}`
							: `${taskType}AlertState`
					}`]: alertState,
				},
			}
		);
	} catch (err) {
		const error = errorCodes['[0E][发送告警信息]更新任务告警状态失败'].from(
			err
		);
		server.log.error({ error }, error.rawMessage);
	}

	return receivers;
}
